﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Threading.Tasks;
using DeepZoomPivotConstructor.VisualCacheInterfaces;
using DeepZoomPivotConstructor.VisualLib;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using RenderEngine;
using RenderEngineInterfaces;
using t_TestingLibrary;
namespace t_RenderEngine
{
    /// <summary>
    /// Test out the composition logic in the render engine.
    /// </summary>
    [TestClass]
    public class t_RenderEngineCompositionUnits
    {
        public t_RenderEngineCompositionUnits()
        {
        }

        private TestContext testContextInstance;

        [TestInitialize]
        public void TestInit()
        {
            Utilities.MyClassInit();
        }

        [TestCleanup]
        public void TestDone()
        {
            Utilities.MyClassDone();
        }

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #region Additional test attributes
        //
        // You can use the following additional attributes as you write your tests:
        //
        // Use ClassInitialize to run code before running the first test in the class
        // [ClassInitialize()]
        // public static void MyClassInitialize(TestContext testContext) { }
        //
        // Use ClassCleanup to run code after all tests in a class have run
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }
        //
        // Use TestInitialize to run code before running each test 
        // [TestInitialize()]
        // public void MyTestInitialize() { }
        //
        // Use TestCleanup to run code after each test has run
        // [TestCleanup()]
        // public void MyTestCleanup() { }
        //
        #endregion


        /// <summary>
        /// Attempt to declare a CU - and cause a total crash.
        /// </summary>
        [Export(typeof(IRenderItem))]
        [RenderInfo(typeof(FileInfo))]
        class RenderSpecialFail : IRenderItem
        {

            public Task<Visual> Render(object what)
            {
                return Task<Visual>.Factory.StartNew(() =>
                {
                    FileInfo f = what as FileInfo;
                    VisualSingleImage vsi = new VisualSingleImage(f, 600);
                    vsi.SetAsComposableUnit(f.LastWriteTime);
                    return vsi;
                });
            }

            public RenderPluginPriority RenderPriority(object what)
            {
                FileInfo f = what as FileInfo;
                if (f.Extension == ".jpg")
                    return RenderPluginPriority.RPPGeneric;
                return RenderPluginPriority.RPPCantDoIt;
            }
        }

        [TestMethod]
        [DeploymentItem("file1.jpg")]
        public void TestNonCompareCU()
        {
            //
            // Make sure that somethign that doesn't know how to do compares (i.e. isn't IRenderCompareObject) can still
            // be marked as a compostable unit.
            //

            t_TestingLibrary.Utilities.AddPart(new RenderSpecialFail());
            RenderControl rc = new RenderControl();
            t_TestingLibrary.Utilities.Compose(rc);
            FileInfo f = new FileInfo(@"file1.jpg");
            Assert.IsTrue(f.Exists);

            bool madeit = true;
            try
            {
                Visual v = rc.Render(f);
            }
            catch
            {
                madeit = false;
            }

            Assert.IsTrue(madeit, "An exception should not have been trhown when we tried to render a non-comparable object.");
        }

        [Export(typeof(IRenderItem))]
        [RenderInfo(typeof(FileInfo))]
        class RenderSpecial : IRenderItem, IRenderableObjectCompare
        {
            public RenderSpecial()
            {
                OutOfDateResult = false;
            }

            public Task<Visual> Render(object what)
            {
                return Task<Visual>.Factory.StartNew(() =>
                    {
                        FileInfo f = what as FileInfo;
                        VisualSingleImage vsi = new VisualSingleImage(f, 600);
                        vsi.SetAsComposableUnit(f.LastWriteTime);
                        return vsi;
                    });
            }

            public RenderPluginPriority RenderPriority(object what)
            {
                FileInfo f = what as FileInfo;
                if (f.Extension == ".jpg")
                    return RenderPluginPriority.RPPGeneric;
                return RenderPluginPriority.RPPCantDoIt;
            }

            public long GetObjectHash(object what)
            {
                return (what as FileInfo).FullName.GetHashCode();
            }

            public bool ObjectsEqual(object o1, object o2)
            {
                return (o1 as FileInfo).FullName == (o2 as FileInfo).FullName;
            }

            public bool OutOfDateResult { get; set; }

            public bool IsOutOfDate(object what, object metaData)
            {
                return OutOfDateResult;
            }


            public object GetObjectDateMetaData(object what)
            {
                throw new NotImplementedException();
            }
        }

        [Export(typeof(IRenderItem))]
        [RenderInfo(typeof(FileInfo))]
        class RenderFileComparable : IRenderItem, IRenderableObjectCompare
        {
            public RenderFileComparable()
            {
                OutOfDateResult = false;
                AddHolder = false;
                InternalCU = false;
            }

            public Task<Visual> Render(object what)
            {
                return Task<Visual>.Factory.StartNew(() =>
                {
                    FileInfo f = what as FileInfo;
                    VisualSingleImage vsi = new VisualSingleImage(f, 600);

                    if (InternalCU == false && !AddHolder)
                        return vsi;

                    if (InternalCU)
                    {
                        vsi.SetAsComposableUnit(DateTime.Now);
                    }

                    VisualHolder h = new VisualHolder();
                    h.AddImage(vsi);
                    return h;
                });
            }

            public RenderPluginPriority RenderPriority(object what)
            {
                FileInfo f = what as FileInfo;
                if (f.Extension == ".jpg")
                    return RenderPluginPriority.RPPGeneric;
                return RenderPluginPriority.RPPCantDoIt;
            }

            public long GetObjectHash(object what)
            {
                return (what as FileInfo).FullName.GetHashCode();
            }

            public bool ObjectsEqual(object o1, object o2)
            {
                return (o1 as FileInfo).FullName == (o2 as FileInfo).FullName;
            }

            public bool OutOfDateResult { get; set; }

            public bool IsOutOfDate(object what, object metaData)
            {
                return OutOfDateResult;
            }

            public object GetObjectDateMetaData(object what)
            {
                return (what as FileInfo).LastWriteTime;
            }

            public bool AddHolder { get; set; }

            public bool InternalCU { get; set; }
        }

        [TestMethod]
        [DeploymentItem("file1.jpg")]
        public void TestAttemptComposable()
        {
            // Since what we are rendering isn't a composable unit, make sure when we push it doesn't fail.

            t_TestingLibrary.Utilities.AddPart(new RenderFileComparable());
            RenderControl rc = new RenderControl();
            t_TestingLibrary.Utilities.Compose(rc);
            FileInfo f = new FileInfo(@"file1.jpg");
            Assert.IsTrue(f.Exists);

            Visual v = rc.Render(f, requestComposableUnit: true);
            // Shoudl fail b/c this type doesn't know how to do a CU!
            Assert.IsTrue(v.IsCompositionUnit, "should be marked composable!");
        }

        [TestMethod]
        [DeploymentItem("file1.jpg")]
        public void TestAttemptComposableGood()
        {
            // With the holder we should not be able to make it composable!

            var fc = new RenderFileComparable();
            fc.AddHolder = true;
            t_TestingLibrary.Utilities.AddPart(fc);
            RenderControl rc = new RenderControl();
            t_TestingLibrary.Utilities.Compose(rc);
            FileInfo f = new FileInfo(@"file1.jpg");
            Assert.IsTrue(f.Exists);

            Visual v = rc.Render(f, requestComposableUnit: true);
            Assert.IsTrue(v.IsCompositionUnit, "should be marked composable!");
        }

        [TestMethod]
        [DeploymentItem("file1.jpg")]
        public void TestForceComposable()
        {
            // Since what we are rendering isn't a composable unit, make sure it is pushed to be one
            // in this case!

            t_TestingLibrary.Utilities.AddPart(new RenderFileComparable());
            RenderControl rc = new RenderControl();
            t_TestingLibrary.Utilities.Compose(rc);
            FileInfo f = new FileInfo(@"file1.jpg");
            Assert.IsTrue(f.Exists);

            Visual v = rc.Render(f, forceAsComposable: true);
            Assert.IsTrue(v.IsCompositionUnit, "should be marked composable!");
        }

        [TestMethod]
        [DeploymentItem("file1.jpg")]
        public void TestAskIfCUInside()
        {
            // When we request CU, if there is a CU already inside, then we shouldn't mark it as a CU.
            var fc = new RenderFileComparable();
            fc.InternalCU = true;
            t_TestingLibrary.Utilities.AddPart(fc);
            RenderControl rc = new RenderControl();
            t_TestingLibrary.Utilities.Compose(rc);
            FileInfo f = new FileInfo(@"file1.jpg");
            Assert.IsTrue(f.Exists);

            Visual v = rc.Render(f, requestComposableUnit: true);
            Assert.IsFalse(v.IsCompositionUnit, "should be marked composable!");
        }

        [TestMethod]
        [DeploymentItem("file1.jpg")]
        public void TestForceIfCUInside()
        {
            // When we request CU, if there is a CU already inside, then we shouldn't mark it as a CU.
            var fc = new RenderFileComparable();
            fc.InternalCU = true;
            t_TestingLibrary.Utilities.AddPart(fc);
            RenderControl rc = new RenderControl();
            t_TestingLibrary.Utilities.Compose(rc);
            FileInfo f = new FileInfo(@"file1.jpg");
            Assert.IsTrue(f.Exists);

            Visual v = rc.Render(f, forceAsComposable: true);
            Assert.IsTrue(v.IsCompositionUnit, "should not be marked composable!");
        }

        [TestMethod]
        [DeploymentItem("file1.jpg")]
        public void TestCompareCU()
        {
            /// Make sure making a composable unit by something that doesn't implement IRenderableCompare
            /// failes horribly.

            t_TestingLibrary.Utilities.AddPart(new RenderSpecial());
            RenderControl rc = new RenderControl();
            t_TestingLibrary.Utilities.Compose(rc);
            FileInfo f = new FileInfo(@"file1.jpg");

            bool madeit = true;
            try
            {
                Visual v = rc.Render(f);
            }
            catch (Exception e)
            {
                System.Diagnostics.Trace.WriteLine(e.Message + e.StackTrace);
                madeit = false;
            }
            Assert.IsTrue(madeit, "Exception thrown when trying to declare CU with a valid render object");
        }

        /// <summary>
        /// Attempt to declare a CU - and cause a total crash.
        /// </summary>
        [Export(typeof(IRenderItem))]
        [RenderInfo(typeof(string))]
        class RenderSpecialCalldown : IRenderItem
        {

            [Import]
            private IRenderEngine MyRender { get; set; }

            public Task<Visual> Render(object what)
            {
                FileInfo f = new FileInfo(what as string);
                return MyRender.RenderToFuture(f);
            }

            public RenderPluginPriority RenderPriority(object what)
            {
                return RenderPluginPriority.RPPGeneric;
            }
        }

        [TestMethod]
        [DeploymentItem("file1.jpg")]
        public void TestRecursiveDownCU()
        {
            /// Make sure making a composable unit by something that doesn't implement IRenderableCompare
            /// failes horribly.

            t_TestingLibrary.Utilities.AddPart(new RenderSpecial());
            t_TestingLibrary.Utilities.AddPart(new RenderSpecialCalldown());
            RenderControl rc = new RenderControl();
            t_TestingLibrary.Utilities.Compose(rc);

            Visual v = rc.Render(@"file1.jpg");
            Assert.IsNotNull(v, "The rendering should work just be fine");
        }

        [TestMethod]
        [DeploymentItem("file1.jpg")]
        public void TestMakeSimpleCU()
        {
            t_TestingLibrary.Utilities.AddPart(new RenderSpecial());
            RenderControl rc = new RenderControl();
            t_TestingLibrary.Utilities.Compose(rc);
            FileInfo f = new FileInfo(@"file1.jpg");
            Assert.IsTrue(f.Exists);

            Visual v = rc.Render(f);
            Assert.IsNotNull(v, "Rendering should have returned a valid visual");
            Assert.IsTrue(v.IsCompositionUnit, "This should have been a compositional unit!");
            Assert.AreEqual(typeof(DateTime), v.CompositionInfo.MetaData.GetType(), "Expecting a type!");
            Assert.AreEqual(f, v.CompositionInfo.RenderObject, "Render object not what we were expecting!");
            Assert.AreEqual("t_RenderEngine.t_RenderEngineCompositionUnits+RenderSpecial", v.CompositionInfo.RenderItemFullType, "Type name not filled correctl!");
            Assert.AreEqual(7.5, v.CompositionInfo.Height, "Height is not equal");
            Assert.AreEqual(10.0, v.CompositionInfo.Width, "Width is not right");
            Assert.AreEqual(f.FullName.GetHashCode(), v.CompositionInfo.Hash, "Hash hasn't been set correctly");
        }

        /// <summary>
        /// Dirt simple DB class to help out of rtesting (i.e. a mock).
        /// </summary>
        class CustomDB : IVisualCacheDB
        {
            private FileInfo myObj = new FileInfo(@"file1.jpg");

            private List<CacheDBInfo> dbinfo = new List<CacheDBInfo>();

            public Visual.VisualInfo Metadata { get; set; }

            public bool DZFileExists { get; set; }
            public bool DZDirExists { get; set; }

            public DirectoryInfo TestContextDir { get; private set; }

            public CustomDB(DirectoryInfo testDir)
            {
                DZFileExists = true;
                DZDirExists = true;
                TestContextDir = testDir;
            }

            private void LoadDB()
            {
                if (dbinfo.Count == 0)
                {
                    CacheDBInfo c = new CacheDBInfo();
                    c.ComposeInfo = new ComposableInfo();
                    c.ComposeInfo.Height = 3.0;
                    c.ComposeInfo.Width = 5.0;
                    c.ComposeInfo.MetaData = myObj.LastWriteTime;
                    c.ComposeInfo.RenderItemFullType = typeof(RenderSpecial).ToString();
                    c.ComposeInfo.RenderObject = myObj;
                    c.Metadata = Metadata;

                    FileInfo dzInfo = new FileInfo(TestContextDir.FullName + "\\bogus.xml");
                    c.DZXMLFile = dzInfo;

                    if (DZFileExists)
                    {
                        if (!TestContextDir.Exists)
                        {
                            TestContextDir.Create();
                        }
                        using (var f = c.DZXMLFile.CreateText())
                        {
                            f.WriteLine("hi");
                            f.Close();
                        }
                    }
                    if (DZDirExists)
                    {
                        if (!TestContextDir.Exists)
                        {
                            TestContextDir.Create();
                        }
                        DirectoryInfo dzDir = new DirectoryInfo(Path.ChangeExtension(dzInfo.FullName, null) + "_files");
                        dzDir.Create();
                    }

                    dbinfo.Add(c);
                }
            }

            /// <summary>
            /// Return an enumerator that will go through the various objects.
            /// </summary>
            /// <param name="hash"></param>
            /// <returns></returns>
            public CacheDBInfo FindHit(long hash)
            {
                if (hash != myObj.FullName.GetHashCode())
                {
                    return null;
                }

                LoadDB();

                return dbinfo[0];
            }

            public void Save(CacheDBInfo info)
            {
                throw new NotImplementedException();
            }
        }

        [TestMethod]
        [DeploymentItem("file1.jpg")]
        public void TestRecallOfCU()
        {
            ///
            /// Test simple recall of the thing!
            /// 

            t_TestingLibrary.Utilities.AddPart(new RenderSpecial());
            RenderControl rc = new RenderControl(new CustomDB(new DirectoryInfo(".\\" + TestContext.TestName)));
            t_TestingLibrary.Utilities.Compose(rc);
            FileInfo f = new FileInfo(@"file1.jpg");

            Visual v = rc.Render(f);
            Assert.AreEqual(0, v.SGNodeList.Count, "There should be no images in this rendering!");
        }

        [Export(typeof(IRenderItem))]
        [RenderInfo(typeof(FileInfo))]
        class RenderSpecialSuper : IRenderItem, IRenderableObjectCompare
        {
            public RenderSpecialSuper()
            {
                OutOfDateResult = false;
            }

            public Task<Visual> Render(object what)
            {
                return Task<Visual>.Factory.StartNew(() =>
                {
                    FileInfo f = what as FileInfo;
                    VisualSingleImage vsi = new VisualSingleImage(f, 600);
                    vsi.SetAsComposableUnit(f.LastWriteTime);
                    return vsi;
                });
            }

            public RenderPluginPriority RenderPriority(object what)
            {
                FileInfo f = what as FileInfo;
                if (f.Extension == ".jpg")
                    return RenderPluginPriority.RPPSpecific;
                return RenderPluginPriority.RPPCantDoIt;
            }

            #region IRenderableObjectCompare Members

            public long GetObjectHash(object what)
            {
                return (what as FileInfo).FullName.GetHashCode();
            }

            public bool ObjectsEqual(object o1, object o2)
            {
                return (o1 as FileInfo).FullName == (o2 as FileInfo).FullName;
            }

            public bool OutOfDateResult { get; set; }

            public bool IsOutOfDate(object what, object metaData)
            {
                return OutOfDateResult;
            }

            #endregion


            public object GetObjectDateMetaData(object what)
            {
                throw new NotImplementedException();
            }
        }

        [TestMethod]
        [DeploymentItem("file1.jpg")]
        public void TestBetterRenderItem()
        {
            ///
            /// When we cache one guy, and then a better render item comes along, it
            /// should re-run the rendering
            /// 

            t_TestingLibrary.Utilities.AddPart(new RenderSpecial());
            t_TestingLibrary.Utilities.AddPart(new RenderSpecialSuper());
            t_TestingLibrary.Utilities.AddPart(new CustomDB(new DirectoryInfo(".\\" + TestContext.TestName)));
            RenderControl rc = new RenderControl();
            t_TestingLibrary.Utilities.Compose(rc);
            FileInfo f = new FileInfo(@"file1.jpg");

            Visual v = rc.Render(f);
            Assert.AreEqual(1, v.SGNodeList.Count, "There should be one image in this rendering!");
        }

        [TestMethod]
        [DeploymentItem("file1.jpg")]
        public void TestComposableUnitNotOutOfDate()
        {
            /// Check that the out of date logic actually works. Reply with out of date,
            /// and make sure that the thing gets re-rendered.

            RenderSpecial rs = new RenderSpecial();
            rs.OutOfDateResult = true;
            t_TestingLibrary.Utilities.AddPart(rs);
            t_TestingLibrary.Utilities.AddPart(new CustomDB(new DirectoryInfo(".\\" + TestContext.TestName)));
            RenderControl rc = new RenderControl();
            t_TestingLibrary.Utilities.Compose(rc);
            FileInfo f = new FileInfo(@"file1.jpg");

            Visual v = rc.Render(f);
            Assert.AreEqual(1, v.SGNodeList.Count, "There should have been a rendering in this guy!");
        }

        [TestMethod]
        [DeploymentItem("file1.jpg")]
        public void TestMetaDataComesThrough()
        {
            /// Check that the meta data is correctly restored when
            /// we are laoding it up

            t_TestingLibrary.Utilities.AddPart(new RenderSpecial());

            CustomDB db = new CustomDB(new DirectoryInfo(".\\" + TestContext.TestName));
            db.Metadata = new Visual.VisualInfo();
            db.Metadata.Title = "hi";

            RenderControl rc = new RenderControl(db);
            t_TestingLibrary.Utilities.Compose(rc);
            FileInfo f = new FileInfo(@"file1.jpg");

            Visual v = rc.Render(f);
            Assert.IsNotNull(v.Info, "Meta data is null");
            Assert.IsNotNull(v.Info.Title, "Title should not be null");
            Assert.AreEqual("hi", v.Info.Title, "Title isn't set correctly");
        }

        [TestMethod]
        [DeploymentItem("file1.jpg")]
        public void TestForMissingCacheLineXMLFile()
        {
            /// User deleted the XML file and data AND/OR directory for a sub-image. Fail to
            /// return that as a cache hit.

            CustomDB db = new CustomDB(new DirectoryInfo(".\\" + TestContext.TestName));
            db.DZFileExists = false;

            t_TestingLibrary.Utilities.AddPart(new RenderSpecial());
            t_TestingLibrary.Utilities.AddPart(db);
            RenderControl rc = new RenderControl();
            t_TestingLibrary.Utilities.Compose(rc);
            FileInfo f = new FileInfo(@"file1.jpg");

            Visual v = rc.Render(f);
            Assert.AreEqual(1, v.SGNodeList.Count, "There should be one images in this rendering because the DZ file does not exist!!");
        }

        [TestMethod]
        [DeploymentItem("file1.jpg")]
        public void TestForMissingCacheLineXMLDir()
        {
            /// User deleted the XML file and data AND/OR directory for a sub-image. Fail to
            /// return that as a cache hit.

            CustomDB db = new CustomDB(new DirectoryInfo(".\\" + TestContext.TestName));
            db.DZDirExists = false;

            t_TestingLibrary.Utilities.AddPart(new RenderSpecial());
            t_TestingLibrary.Utilities.AddPart(db);
            RenderControl rc = new RenderControl();
            t_TestingLibrary.Utilities.Compose(rc);
            FileInfo f = new FileInfo(@"file1.jpg");

            Visual v = rc.Render(f);
            Assert.AreEqual(1, v.SGNodeList.Count, "There should be one images in this rendering because the DZ file does not exist!!");
        }

    }
}
